# frozen_string_literal: true

module ROM
  # Helper module for dataset classes
  #
  # It provides a constructor accepting data, header and an optional row_proc.
  # This module is used internally by EnumerableDataset and ArrayDataset.
  #
  # @private
  module DataProxy
    NON_FORWARDABLE = %i[
      each to_a to_ary kind_of? instance_of? is_a?
    ].freeze

    # Wrapped data array
    #
    # @return [Object] Data object for the iterator
    #
    # @api private
    attr_reader :data

    # @return [Proc] tuple processing proc
    #
    # @api private
    attr_reader :row_proc

    # Extends the class with `forward` DSL and Equalizer using `data` attribute
    #
    # @see ClassMethods#forward
    #
    # @api private
    def self.included(klass)
      klass.class_eval do
        extend ClassMethods

        include ::Dry::Equalizer(:data)

        option :row_proc, default: -> { self.class.row_proc }
      end
    end

    # Iterate over data using row_proc
    #
    # @return [Enumerator] if block is not given
    #
    # @api private
    def each
      return to_enum unless block_given?

      data.each { |tuple| yield(row_proc[tuple]) }
    end

    module ClassMethods
      # Default no-op tuple proc
      #
      # @return [Proc]
      #
      # @api private
      def row_proc = -> tuple { tuple }

      # Forward provided methods to the underlaying data object
      #
      # @example
      #
      #   class MyDataset
      #     include DataProxy
      #
      #     forward(:find_all, :map)
      #   end
      #
      # @return [undefined]
      #
      # @api public
      def forward(*methods)
        # FIXME: we should probably raise if one of the non-forwardable methods
        #       was provided
        (methods - NON_FORWARDABLE).each do |method_name|
          class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
            def #{method_name}(...)                              # def find_all(...)
              response = data.public_send(:#{method_name}, ...)  #   response = data.public_send(:find_all, ...)
                                                                 #
              if response.equal?(data)                           #   if response.equal?(data)
                self                                             #     self
              elsif response.is_a?(data.class)                   #   elsif response.is_a?(data.class)
                self.class.new(response)                         #     self.class.new(response)
              else                                               #   else
                response                                         #     response
              end                                                #   end
            end                                                  # end
          RUBY
        end
      end
    end
  end
end
